package com.zcgk.modules.file.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sun.org.apache.xml.internal.utils.AttList;
import com.zcgk.config.UserDefineConfig;
import com.zcgk.core.domain.CodeMsg;
import com.zcgk.core.exception.CosyException;
import com.zcgk.modules.file.entity.AttachmentEntity;
import com.zcgk.modules.file.entity.vo.req.AddAttachmentReqVO;
import com.zcgk.modules.file.entity.vo.resp.DownloadAttachmentRespVO;
import com.zcgk.modules.file.mapper.AttachmentMapper;
import com.zcgk.modules.file.service.IAttachmentService;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * <p>
 * 附件表服务实现类
 * </p>
 *
 * @author Francis
 * @since 2020-02-20
 */
@Service
public class AttachmentServiceImpl extends ServiceImpl<AttachmentMapper, AttachmentEntity> implements IAttachmentService {

    @Resource
    AttachmentMapper attachmentMapper;

    @Autowired
    UserDefineConfig userDefineConfig;


    @Override
    @Transactional(rollbackFor = Exception.class)
    public String add(MultipartFile file, AddAttachmentReqVO reqVO) {
        if (StringUtils.isEmpty(reqVO.getSystemIdFk())) {
            log.error("系统id不能为空！");
            throw new CosyException(CodeMsg.SYSTEM_ID_IS_EMPTY);
        }
        if (StringUtils.isEmpty(reqVO.getResIdFk())) {
            log.error("资源id不能为空！");
            throw new CosyException(CodeMsg.RESOURCE_ID_IS_EMPTY);
        }

        // 1. 上传文件
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        String attName = file.getOriginalFilename();
        String attType = FilenameUtils.getExtension(attName);
        BigDecimal attSize = new BigDecimal(file.getSize()).divide(new BigDecimal(1024));
        String fileName = uuid + "." + attType;

        // 设置文件path
        String sysCode = "tf";
//        String sysCode = 调用接口获取
        String table = reqVO.getRemark().matches("[A-Za-z0-9_]+") ? "/" + reqVO.getRemark() : "";
        String tempPath = sysCode + table;

        File objFile = new File(userDefineConfig.physicalPath + tempPath + "/" + fileName);
        if (!objFile.isDirectory()) {
            objFile.mkdirs();
        }

        try {
            file.transferTo(objFile);
        } catch (IOException e) {
            log.error("文件上传失败！", e);
            throw new CosyException(CodeMsg.FILE_UPLOAD_ERROR);
        }

        // 2. 入库
        AttachmentEntity att = new AttachmentEntity();
        BeanUtils.copyProperties(reqVO, att);
        att.setAttachmentId(uuid);
        att.setAttachmentName(attName);
        att.setAttachmentType(attType);
        att.setAttachmentSize(attSize);
        att.setAttachmentUrl(userDefineConfig.getVirtualPath() + tempPath + "/" + fileName);

        try {
            attachmentMapper.insert(att);
        } catch (Exception e) {
            log.error("文件记录保存失败！", e);
            throw new CosyException(CodeMsg.FILE_UPLOAD_ERROR);
        }

        return uuid;
    }


    @Override
    public List<String> queryUrlListByResId(String resId) {
        if (StringUtils.isEmpty(resId)) {
            log.error("资源id不能为空！");
            throw new CosyException(CodeMsg.RESOURCE_ID_IS_EMPTY);
        }

        List<String> urlList = null;
        try {
            urlList = attachmentMapper.queryUrlListByResId(resId, null, null);
        } catch (Exception e) {
            log.error("根据资源id查询路径过程中出现异常！", e);
            throw new CosyException(CodeMsg.FILE_PATH_FIND_ERROR);
        }

        return urlList;
    }


    @Override
    public List<String> queryUrlListByResId4OldSys(String resId, String systemId, String tableName) {
        validateParam4OldSys(resId, systemId, tableName);

        List<String> urlList = null;
        try {
            urlList = attachmentMapper.queryUrlListByResId(resId, systemId, tableName);
        } catch (Exception e) {
            log.error("根据资源id查询路径过程中出现异常！", e);
            throw new CosyException(CodeMsg.FILE_PATH_FIND_ERROR);
        }

        return urlList;
    }


    @Override
    public DownloadAttachmentRespVO downloadByResId(String resId) {
        if (StringUtils.isEmpty(resId)) {
            log.error("资源id不能为空！");
            throw new CosyException(CodeMsg.RESOURCE_ID_IS_EMPTY);
        }

        List<AttachmentEntity> attList = null;
        try {
            attList = attachmentMapper.selectList(new QueryWrapper<AttachmentEntity>().eq("res_id_fk", resId)
                    .select("attachment_id, attachment_name", "attachment_url"));
        } catch (Exception e) {
            log.error("根据 资源id 查询附件的过程中出现异常！", e);
            throw new CosyException(CodeMsg.DB_ERROR);
        }

        return handleDownload(attList);
    }


    @Override
    public DownloadAttachmentRespVO downloadByResId4OldSys(String resId, String systemId, String tableName) {
        validateParam4OldSys(resId, systemId, tableName);

        List<AttachmentEntity> attList = null;
        try {
            attList = attachmentMapper.selectList(new QueryWrapper<AttachmentEntity>()
                    .eq("res_id_fk", resId).eq("system_id_fk", systemId).eq("remark", tableName)
                    .select("attachment_id, attachment_name", "attachment_url"));
        } catch (Exception e) {
            log.error("根据 资源id、系统id、表名 查询附件的过程中出现异常！", e);
            throw new CosyException(CodeMsg.DB_ERROR);
        }

        return handleDownload(attList);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(String attId) {
        if (StringUtils.isEmpty(attId)) {
            log.error("附件主键id不能为空！");
            throw new CosyException(CodeMsg.ATTACHMENT_ID_IS_EMPTY);
        }

        AttachmentEntity attachment = null;
        try {
            attachment = attachmentMapper.selectOne(new QueryWrapper<AttachmentEntity>().eq("attachment_id", attId)
                    .select("attachment_url"));
        } catch (Exception e) {
            log.error("根据 附件主键id 查询附件过程中出现异常！", e);
            throw new CosyException(CodeMsg.DB_ERROR);
        }

        if (Objects.isNull(attachment)) {
            log.error("没有找到对应的文件");
            throw new CosyException(CodeMsg.DATA_IS_EMPTY);
        }

        // 1. 执行delete
        try {
            attachmentMapper.deleteById(attId);
        } catch (Exception e) {
            log.error("根据 附件主键id 删除文件记录过程中出现异常！", e);
            throw new CosyException(CodeMsg.DATA_NO_EFFECT);
        }

        // 2. 从硬盘上删除文件
        try {
            deleteFromDisk(attachment.getAttachmentUrl());
        } catch (Exception e) {
            log.error("根据 附件主键id 删除文件过程中出现异常！", e);
            throw new CosyException(CodeMsg.DATA_NO_EFFECT);
        }
    }


    @Override
    public void deleteByResId(String resId) {
        if (StringUtils.isEmpty(resId)) {
            log.error("资源id不能为空！");
            throw new CosyException(CodeMsg.RESOURCE_ID_IS_EMPTY);
        }

        List<AttachmentEntity> attList = null;
        try {
            attList = attachmentMapper.selectList(new QueryWrapper<AttachmentEntity>().eq("res_id_fk", resId)
                    .select("attachment_url"));
        } catch (Exception e) {
            log.error("根据 资源id 查询附件过程中出现异常！", e);
            throw new CosyException(CodeMsg.DB_ERROR);
        }

        if (attList.size() == 0) {
            log.error("没有找到对应的文件！");
            throw new CosyException(CodeMsg.DATA_IS_EMPTY);
        }

        // 1. 执行 delete
        try {
            attachmentMapper.delete(new QueryWrapper<AttachmentEntity>().eq("res_id_fk", resId));
        } catch (Exception e) {
            log.error("根据 资源id 删除文件记录过程中出现异常！", e);
            throw new CosyException(CodeMsg.DATA_NO_EFFECT);
        }

        // 2. 从硬盘上删除文件(循环删除)
        attList.forEach(att -> {
            try {
                deleteFromDisk(att.getAttachmentUrl());
            } catch (Exception e) {
                log.error("根据 资源id 删除文件过程中出现异常！当前附件主键id为：" + att.getAttachmentId(), e);
                throw new CosyException(CodeMsg.DATA_NO_EFFECT);
            }
        });
    }


    @Override
    public void deleteByResId4OldSys(String resId, String systemId, String tableName) {
        validateParam4OldSys(resId, systemId, tableName);

        List<AttachmentEntity> attList = null;
        try {
            attList = attachmentMapper.selectList(new QueryWrapper<AttachmentEntity>()
                    .eq("res_id_fk", resId).eq("system_id_fk", systemId).eq("remark", tableName)
                    .select("attachment_url"));
        } catch (Exception e) {
            log.error("根据 资源id、系统id、表名 查询附件过程中出现异常！", e);
            throw new CosyException(CodeMsg.DB_ERROR);
        }

        if (attList.size() == 0) {
            log.error("没有找到对应的文件！");
            throw new CosyException(CodeMsg.DATA_IS_EMPTY);
        }

        // 1. 执行 delete
        try {
            attachmentMapper.delete(new QueryWrapper<AttachmentEntity>()
                    .eq("res_id_fk", resId).eq("system_id_fk", systemId).eq("remark", tableName));
        } catch (Exception e) {
            log.error("根据 资源id 删除文件记录过程中出现异常！", e);
            throw new CosyException(CodeMsg.DATA_NO_EFFECT);
        }

        // 2. 从硬盘上删除文件(循环删除)
        attList.forEach(att -> {
            try {
                deleteFromDisk(att.getAttachmentUrl());
            } catch (Exception e) {
                log.error("根据 资源id 删除文件过程中出现异常！当前附件主键id为：" + att.getAttachmentId(), e);
                throw new CosyException(CodeMsg.DATA_NO_EFFECT);
            }
        });
    }



    /**
     * 处理文件下载
     *
     * @param attList 附件list
     * @return 可下载源
     */
    private DownloadAttachmentRespVO handleDownload (List<AttachmentEntity> attList) {
        DownloadAttachmentRespVO respVO = null;

        String attName = null;
        byte[] data = null;
        if (attList.size() == 1) {
            // 单文件
            try {
                attName = attList.get(0).getAttachmentName();
                attName = URLEncoder.encode(attName, "UTF-8");
                attName = new String(attName.getBytes(), "iso-8859-1");

                String filePath = userDefineConfig.getPhysicalPath() +
                        attList.get(0).getAttachmentUrl().split(userDefineConfig.getVirtualPath())[1];
                data = FileUtils.readFileToByteArray(new File(filePath));

                respVO = new DownloadAttachmentRespVO(attName, data);
            } catch (IOException e) {
                log.error("下载文件过程中出现异常！", e);
                throw new CosyException(CodeMsg.DOWNLOAD_FILE_ERROR);
            }
        } else if (attList.size() > 1) {
            // 多文件
            respVO = getZipSourceData(attList);
        } else {
            log.error("没有找到对应的文件");
            throw new CosyException(CodeMsg.DATA_IS_EMPTY);
        }

        return respVO;
    }


    /**
     * 将 list 中的文件写入zip临时文件
     * 获取 zip临时文件数据
     * 删除 zip临时文件
     *
     * @param attList 附件list
     * @return 可下载源
     */
    private DownloadAttachmentRespVO getZipSourceData (List<AttachmentEntity> attList) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssS");
        String attName = sdf.format(new Date()) + ".zip";

        // 准备压缩文件内容
        Map<String, byte[]> fileMap = new HashMap<>();
        try {
            for (AttachmentEntity att : attList) {
                String[] tempArr = att.getAttachmentName().split("\\.");
                String tempName = tempArr[0] + "_" + att.getAttachmentId() + "." + tempArr[1];
                String tempPath = userDefineConfig.getPhysicalPath() +
                        att.getAttachmentUrl().split(userDefineConfig.getVirtualPath())[1];
                byte[] data = FileUtils.readFileToByteArray(new File(tempPath));

                fileMap.put(tempName, data);
            }
        } catch (IOException e) {
            log.error("读取文件数据失败！", e);
            throw new CosyException(CodeMsg.DOWNLOAD_FILE_ERROR);
        }

        // 创建临时文件
        File zipFile = new File(userDefineConfig.getPhysicalPath() + attName);
        ZipOutputStream zipOutputStream = null;

        try {
             zipOutputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)));

            // 写入压缩文件内容
            for (Map.Entry<String, byte[]> entry : fileMap.entrySet()) {
                InputStream in = new BufferedInputStream(new ByteArrayInputStream(entry.getValue()));

                byte[] b = new byte[1024];
                zipOutputStream.putNextEntry(new ZipEntry(entry.getKey()));
                int len = -1;
                while ((len = in.read(b)) != -1) {
                    zipOutputStream.write(b, 0, len);
                }
            }

            zipOutputStream.close();
        } catch (IOException e) {
            log.error("向zip写入文件过程中出现异常！", e);
            throw new CosyException(CodeMsg.DOWNLOAD_FILE_ERROR);
        }

        // 获取压缩文件数据
        byte[] data = new byte[0];
        try {
            data = FileUtils.readFileToByteArray(zipFile);
        } catch (IOException e) {
            log.error("获取zip文件数据过程中出现异常！");
            throw new CosyException(CodeMsg.DOWNLOAD_FILE_ERROR);
        }

        // 删除临时文件
        zipFile.delete();

        return new DownloadAttachmentRespVO(attName, data);
    }


    /**
     * 根据 文件映射路径存放地址 从硬盘上 删除文件
     *  注：此处需与配置文件中自定义硬盘路径、映射路径搭配使用
     *
     * @param attachmentUrl 文件映射路径存放地址
     */
    private void deleteFromDisk (String attachmentUrl) {
        String filePath = userDefineConfig.getPhysicalPath() + attachmentUrl.split(userDefineConfig.getVirtualPath())[1];
        File file = new File(filePath);
        file.delete();
    }


    /**
     * 对老系统开发的接口的入参校验
     *
     * @param resId 资源id
     * @param systemId 系统id
     * @param tableName 表名
     */
    private void validateParam4OldSys (String resId, String systemId, String tableName) {
        if (StringUtils.isEmpty(resId)) {
            log.error("资源id不能为空！");
            throw new CosyException(CodeMsg.RESOURCE_ID_IS_EMPTY);
        }
        if (StringUtils.isEmpty(systemId)) {
            log.error("系统id不能为空！");
            throw new CosyException(CodeMsg.SYSTEM_ID_IS_EMPTY);
        }
        if (StringUtils.isEmpty(tableName)) {
            log.error("表名不能为空！");
            throw new CosyException(CodeMsg.TABLE_NAME_IS_EMPTY);
        }
    }
}
